/*
 * 
 * iFinity Software Development - http://www.ifinity.com.au
 * Copyright (c) 2006 - 2009
 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the "Software"), the rights to use, copy, modify, merge, publish, distribute, sublicense
 * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
 * following conditions:

 * 1. The above copyright notice and this permission notice shall be included in all copies or substantial portions 
 *    of the Software.  
 * 2. The software may not be claimed as the original work and may not be sold as a stand alone product

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

/* DNN Specific modules */
using DotNetNuke.Entities.Portals;
using DotNetNuke.Common;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Modules.Definitions;
using DotNetNuke.Services.Localization;

/* module specific references */
using DotNetNuke.Providers.SearchEngineSiteMapProvider;
using DotNetNuke.Providers.BlogSiteMapProvider.Entities;

namespace DotNetNuke.Providers.BlogSiteMapProvider
{
    public class BlogSiteMapProvider : SiteMapProvider
    {
        #region protected members
        protected int _tabID=0;
        protected int _moduleID;
        protected bool _showArchives;
        protected bool _showDays;
        protected bool _showMonths;
        protected bool _usePermalinks = true;
        #endregion

        #region Overriden Base Members
        public BlogSiteMapProvider(string name, System.Collections.Specialized.NameValueCollection config) 
            : base(name, config)
        {
            //constructor
            _showArchives = GetConfigBoolSetting(config, "showArchives", false);
            _showDays = GetConfigBoolSetting(config, "showDays", _showArchives);
            _showMonths = GetConfigBoolSetting(config, "showMonths", _showArchives);
            _usePermalinks = GetConfigBoolSetting(config, "usePermaLinks", true);
            
            if (_showDays || _showMonths)
                _showArchives = true;
        }

        public override List<SitePage> SitePagesForTab(PortalAliasInfo portalAlias, TabInfo tab, Locale locale, int sitemapNum, int urlCount, int startingCount, int endingCount)
        {
            List<SitePage> sitePages = new List<SitePage>();
            _tabID = tab.TabID;
            _friendlyURLs = (DotNetNuke.Entities.Host.HostSettings.GetHostSetting("UseFriendlyUrls") == "Y");
            sitePages = GetPageURLsForBlogPage(portalAlias, locale);
            return sitePages;
        }
        private bool GetConfigBoolSetting(System.Collections.Specialized.NameValueCollection config, string attributeName, bool defaultValue)
        {
            bool result = defaultValue;
            string raw = config[attributeName];
            if (!bool.TryParse(raw, out result))
                result = defaultValue; //explicitly set, because tryparse will return false if can't be parsed
            return result;
        }
        #endregion

        #region private Blog-specific members
        /// <summary>
        /// This procedures scans the specified page, locates the Modules on the page which are Blog module specific, 
        /// and returns a collection of SitePage objects relating to each separate URL the blog entries can be found under
        /// </summary>
        /// <param name="siteURL">The URL (including the TabID) for the blog page.</param>
        /// <returns>List<SitePage> collection of SitePage objects.</returns>
        private List<SitePage> GetPageURLsForBlogPage(PortalAliasInfo portalAlias, Locale locale)
        {
            //initialise the master collection of SitePage objects
            List<SitePage> pageURLs = new List<SitePage>();
            base.SetBaseURL(portalAlias.HTTPAlias); //get the base url and portal alias url
            //get the tab, portal and blog information
            if (portalAlias == null)
                throw new Exception("No PortalInfo found for URL (" + _portalAliasURL + ")");
                
            TabController tabController = new TabController();
            TabInfo tab = tabController.GetTab(_tabID, portalAlias.PortalID, false);
            //635: get the sitemap page priority from the tab if it is avialable
            _defaultPagePriority = GetTabPriority(_defaultPagePriority, tab);
            
            ModuleController modCtl = new ModuleController();
            ModuleDefinitionController modDefs = new ModuleDefinitionController();
            //get the list of modules for the page
            tab.Modules = modCtl.GetPortalTabModules(portalAlias.PortalID, tab.TabID);
            if (tab.Modules != null)
            {
                foreach (ModuleInfo mod in tab.Modules)
                {
                    //check each modeul on the page, looking for the blog-specific ones
                    List<SitePage> modulePages = new List<SitePage>();
                    ModuleDefinitionInfo modDef = modDefs.GetModuleDefinition(mod.ModuleDefID);
                    string modDefFriendlyName = modDef.FriendlyName;
                    switch (modDefFriendlyName.ToLower())
                    {
                        case "view_blog":
                            //get entries for blog
                            pageURLs.AddRange(GetBlogURLs(modDef, mod, tab, locale));
                            break;
                        case "blog_archive":
                            //get entries for blog archive
                            if (_showArchives)
                                pageURLs.AddRange(GetArchiveBlogURLs(modDef, mod, tab, locale));
                            break;
                    }
                    
                }
            }
            return pageURLs;
        }
        /// <summary>
        /// Builds the list of Blog URL's into a SitePage List based on a specific Blog module.
        /// </summary>
        /// <param name="modDef">The Module Definition object.</param>
        /// <param name="mod">The Module object</param>
        /// <param name="tab">The Tab object</param>
        /// <param name="blogs">The list of blogs for the portal</param>
        /// <param name="blogSettings">The list of settings relating to that blog</param>
        /// <returns>List of SitePage objects.</returns>
        private List<SitePage> GetBlogURLs(ModuleDefinitionInfo modDef, ModuleInfo mod, TabInfo tab,Locale locale)
        {
            //we need to construct a series of URLs that are accessible for this blog
            //these are the combinations per blog
            //blogid/1
            //entryID/2
            List<EntryInfo> entries = null; List<BlogInfo> blogs = null;
            int count = Data.DataProvider.Instance().GetBlogEntries(mod.PortalID, mod.ModuleID, out entries, out blogs);
            List<SitePage> urls = new List<SitePage>();
            Dictionary<int, DateTime> blogFirstEntry = new Dictionary<int,DateTime>();
            Dictionary<int, DateTime> blogSecondEntry = new Dictionary<int,DateTime>();

            foreach (EntryInfo entry in entries)
            {
                urls.Add(SitePageFromEntry(tab, locale, entry));
                //keep the most recent two blog updates 
                if (blogFirstEntry.ContainsKey(entry.BlogId) == false)
                {
                    blogFirstEntry.Add(entry.BlogId, entry.AddedDate);
                }
                else
                {
                    if (blogSecondEntry.ContainsKey(entry.BlogId) == false)
                        blogSecondEntry.Add(entry.BlogId, entry.AddedDate);
                }
            }
            foreach (BlogInfo blog in blogs)
            {
                //get the entries for the blog
                DateTime firstEntryDate = DateTime.MinValue, secondEntryDate = DateTime.MinValue;
                if (blogFirstEntry.ContainsKey(blog.BlogId))
                    firstEntryDate = blogFirstEntry[blog.BlogId];
                if (blogSecondEntry.ContainsKey(blog.BlogId))
                    secondEntryDate = blogSecondEntry[blog.BlogId];
                urls.Add(SitePageFromBlog(tab, locale, blog, firstEntryDate, secondEntryDate));
            }
            return urls;
        }
        /// <summary>
        /// Returns a list of URL's relating to the Blog archives.
        /// </summary>
        /// <param name="modDef">The Module Definition object.</param>
        /// <param name="mod">The Module object</param>
        /// <param name="tab">The Tab object</param>
        /// <param name="blogs">The list of blogs for the portal</param>
        /// <param name="blogSettings">The list of settings relating to that blog</param>
        /// <returns>List of SitePage objects.</returns>
        private List<SitePage> GetArchiveBlogURLs(ModuleDefinitionInfo modDef, ModuleInfo mod, TabInfo tab, Locale locale)
        {
            List<SitePage> blogArchivePages = new List<SitePage>();
            List<ArchiveInfo> archiveDates = new List<ArchiveInfo>();
            Data.DataProvider.Instance().GetBlogArchives(mod.PortalID, mod.ModuleID, out archiveDates);
            List<DateTime> usedDates =new List<DateTime>();
            List<DateTime> usedMonths = new List<DateTime>();
            foreach (ArchiveInfo archiveDate in archiveDates)
            {
                List<string> usedDays = new List<string>();
                if (_showDays)
                {
                    DateTime thisDay = new DateTime(archiveDate.Year, archiveDate.Month, archiveDate.Day);
                    if (usedDates.Contains(thisDay) == false) //note : dates repeat per blog
                    {
                        string url = GetTabURL(tab, locale, thisDay, "DateType", "day");

                    
                        //don't repeat archive days
                        //http://localhost/dnnDev/Default.aspx?tabid=54&BlogDate=2006-10-11 -- all in month up to that day
                        //http://localhost/dnnDev/Default.aspx?tabid=54&BlogDate=2006-10-31 -- all in month
                        //http://localhost/dnnDev/Default.aspx?tabid=53&&BlogId=1&BlogDate=2006-10-31
                        //http://localhost/dnnDev/Default.aspx?tabid=53&&BlogDate=2006-10-31 -- bizarre!
                        //http://localhost/dnnDev/Blog/tabid/53/BlogId/1/BlogDate/2006-10-31/Default.aspx
                        //http://localhost/dnnDev/Blog/tabid/53/BlogDate/2006-10-31/Default.aspx
                        //assume all archived blogs are no longer updated.
                        blogArchivePages.Add(new SitePage(url, thisDay, ChangeFrequencies.never, (_defaultPagePriority / 2)));
                        usedDates.Add(thisDay);
                    }
                }
                if (_showMonths)
                {
                    //add in the month variable
                    DateTime thisMonth = new DateTime(archiveDate.Year, archiveDate.Month, 1);
                    if (usedMonths.Contains(thisMonth) == false)
                    {
                        string urlMonth = GetTabURL(tab, locale, thisMonth, "DateType", "month");
                        blogArchivePages.Add(new SitePage(urlMonth, thisMonth, ChangeFrequencies.never, (_defaultPagePriority / 2)));
                        usedMonths.Add(thisMonth);
                    }
                }
            }
            return blogArchivePages;
        }
        /// <summary>
        /// Returns a SitePage object from the supplied Tab and Blog information.
        /// </summary>
        /// <param name="tab">The tab object the blog is on.</param>
        /// <param name="blog">The blog object</param>
        /// <param name="blogEntries">The ArrayList of entries for the specified blog.</param>
        /// <returns></returns>
        private SitePage SitePageFromBlog(TabInfo tab,Locale locale, BlogInfo blog, DateTime firstEntryDate, DateTime secondEntryDate)
        {
            ChangeFrequencies changeFreq = GetBlogUpdateFrequency(firstEntryDate, secondEntryDate);
            string url = GetTabURL(tab,locale, "BlogID", blog.BlogId);
            return new SitePage(url, blog.LastEntry, changeFreq, _defaultPagePriority);
        }
        /// <summary>
        /// Returns a SitePage object constructed from the supplied Blog Entry information.
        /// </summary>
        /// <param name="tab">The Tab object on which the blog entry is located.</param>
        /// <param name="blog">The blog object which is the parent of the Entry</param>
        /// <param name="entry">The blog entry object.</param>
        /// <returns>A SItePage object5</returns>
        private SitePage SitePageFromEntry(TabInfo tab,Locale locale, EntryInfo entry)
        {
            ChangeFrequencies changeFreq = ChangeFrequencies.never;//default never change once the entry is in
            DateTime mostRecentChange;
            DateTime secondMostRecentChange;
            
            if (entry.LatestCommentDate > DateTime.MinValue)
            {
                mostRecentChange = entry.LatestCommentDate;
                secondMostRecentChange = entry.AddedDate;
                
            }
            else 
            {
                mostRecentChange = entry.AddedDate;
                secondMostRecentChange = entry.AddedDate;
            }
            TimeSpan between = mostRecentChange - secondMostRecentChange;
            long daysBetween = between.Days;
            if (daysBetween < 2)
                changeFreq = ChangeFrequencies.daily;
            else if (daysBetween <14)
                changeFreq = ChangeFrequencies.weekly;
            else if (daysBetween <60)
                changeFreq = ChangeFrequencies.monthly;
            else if (daysBetween <500)
                changeFreq = ChangeFrequencies.yearly;
            else 
                changeFreq = ChangeFrequencies.never;

            string url = null;
            //715 : use permalinks to show SEO blog entries
            if (_usePermalinks)
                url = entry.Permalink;
            else
                url = GetTabURL(tab, locale, "EntryID", entry.EntryId);

            return new SitePage(url, mostRecentChange, changeFreq, _defaultPagePriority);
        }
        /// <summary>
        /// Returns the update frequency of a blog, based on the latest entries to it.
        /// </summary>
        /// <param name="blogEntries">the list of entries for blog.</param>
        /// <returns>A ChangeFrequencies enumerated type value.</returns>
        private ChangeFrequencies GetBlogUpdateFrequency(DateTime firstEntryDate, DateTime secondEntryDate)
        {
            ChangeFrequencies changeFreq = ChangeFrequencies.never;
            //work out how often the blog page is updated by looking at the frequency of the blog updates
            TimeSpan diff = secondEntryDate - firstEntryDate;
            long daysBetween = Math.Abs(diff.Days);
            if (daysBetween < 2)
                changeFreq = ChangeFrequencies.daily;
            else if (daysBetween < 14)
                changeFreq = ChangeFrequencies.weekly;
            else if (daysBetween < 60)
                changeFreq = ChangeFrequencies.monthly;
            else if (daysBetween < 500)
                changeFreq = ChangeFrequencies.yearly;
            else
                changeFreq = ChangeFrequencies.never;
            return changeFreq;
        }
        /// <summary>
        /// Pulls the tab ID out of a supplied URL.
        /// </summary>
        /// <param name="siteURL">The URL of the tab</param>
        private void StripTabID(string siteURL)
        {
            //we are expecting the siteURL to be a specific tab URL
            string[] urlParts = siteURL.Split('/','?','=');
            //example blog tab ID
            //http://www.ifinity.com.au/Blog/tabid/65/Default.aspx
            //http://www.ifinity.com.au/Default.aspx?tabid=65
            //http://www.ifinity.com.au/Blogs/TechnicalBlog/tabid/60/EntryID/9/Default.aspx
            //http://www.ifinity.com.au/Blog/tabid/65/BlogDate/2006-10-31/Default.aspx
            bool nextBitTabID = false, nextBitEntryID = false;
            foreach (string urlBit in urlParts)
            {
                if (nextBitTabID)
                {
                    //this should be the TabID
                    if (int.TryParse(urlBit, out _tabID))
                    {
                        nextBitTabID = false;
                    }
                }
                if (nextBitEntryID)
                {
                    
                    //this should be the entryID
                    nextBitEntryID = false;
                }
                switch (urlBit.ToLower())
                {
                    case "tabid":
                       nextBitTabID = true;
                       break;
                    case "entryid":
                        nextBitEntryID = true;
                        break;
                }
            }
        }
        /// <summary>
        /// Returns a URL formatted from the TabInfo object, plus an ID name and value.  This uses friendly URL format
        /// if required, or simply appends the id name and value as querystring parameters.
        /// </summary>
        /// <param name="ti"></param>
        /// <param name="idName"></param>
        /// <param name="idValue"></param>
        /// <returns></returns>
        protected string GetTabURL(TabInfo ti, Locale locale, string idName, int idValue)
        {
            return GetTabURL(ti, locale, idName, idValue.ToString());
        }
        /// <summary>
        /// Returns a URL formatted from the TabInfo object, plus an ID name and value.  This uses friendly URL format
        /// if required, or simply appends the id name and value as querystring parameters.
        /// </summary>
        /// <param name="ti">TabInfo object</param>
        /// <param name="idName">The name of the ID value</param>
        /// <param name="idValue">The ID valeu to append with the ID Name.</param>
        /// <returns>string URL value.</returns>
        protected string GetTabURL(TabInfo ti, Locale locale, string idName, string idValue)
        {
            string langCode = "";
            if (locale != null)
                langCode = locale.Code;
            string url = DotNetNuke.Common.Globals.NavigateURL(ti.TabID,false, _portalSettings,"", langCode, idName, idValue.ToString());
            url = EnsureAliasOnUrl(url);
            return url;
        }
        /// <summary>
        /// Returns a URL formatted from the TabInfo object, plus an ID name and value.  This uses friendly URL format
        /// if required, or simply appends the id name and value as querystring parameters.  This procedure is specifically for 
        /// BlogDate parameters.
        /// </summary>
        /// <param name="ti">TabInfo object for the tab the blog is on.</param>
        /// <param name="blogDate">The blog date</param>
        /// <returns></returns>
        protected string GetTabURL(TabInfo ti, Locale locale, DateTime blogDate, string dateTypeKey, string dateTypeName)
        {
            string langCode = "";
            string url ="";
            if (locale != null)
            {
                langCode = locale.Code;
                url = DotNetNuke.Common.Globals.NavigateURL(ti.TabID, false, _portalSettings, "", langCode, "BlogDate", blogDate.ToString("yyyy-MM-dd"), dateTypeKey, dateTypeName);
            }
            else
                url = Globals.NavigateURL(ti.TabID, "", "BlogDate", blogDate.ToString("yyyy-MM-dd"), dateTypeKey, dateTypeName);
            url = EnsureAliasOnUrl(url);
            return url;
        }
        private string EnsureAliasOnUrl(string url)
        {
            string httpAndPortalAlias = DotNetNuke.Common.Globals.AddHTTP(_portalAliasURL);
            if (!url.StartsWith(httpAndPortalAlias))
            {
                if (url.StartsWith("http://"))
                {
                    //it's not the right alias, but at least it's http
                    //do nothing
                }
                else
                {
                    if (url.StartsWith(_portalAliasURL))
                        url = Globals.AddHTTP(url);
                    else
                    {
                        if (!url.StartsWith("/"))
                            url = "/" + url;
                        url = httpAndPortalAlias + url;
                    }
                }
            }
            return url;
        }
        #endregion
    }
}
